/* hexedit -- Hexadecimal Editor for Binary Files
   Copyright (C) 1998 Pixel (Pascal Rigaux)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/
#include "bookmarks.h"
#include "dhexview.h"


static void goto_char(char);
static void save_buffer(void);
static void escaped_command(void);
static void help(void);
static void short_help(void);

/*******************************************************************************/
/* interactive functions */
/*******************************************************************************/

static void next_line(void)
{
    move_cursor(+lineLength);
}

static void previous_line(void)
{
    move_cursor(-lineLength);
}

static void forward_chars(void)
{
    move_cursor(+blocSize);
}

static void backward_chars(void)
{
    move_cursor(-blocSize);
}

static void next_lines(void)
{
    move_cursor(+lineLength * blocSize);
}

static void previous_lines(void)
{
    move_cursor(-lineLength * blocSize);
}

static void beginning_of_line(void)
{
    cursorOffset = 0;
    move_cursor(-(cursor % lineLength));
}

static void
move_to_next_page (void)
{
    int pageno = (base + cursor) / dhexview_options.page_size;
    set_cursor((pageno + 1) * dhexview_options.page_size);
    return;
}

static void
move_to_prev_page (void)
{
    int pageno = (base + cursor) / dhexview_options.page_size;
    int remainder = (base + cursor) % dhexview_options.page_size;
    if (pageno > 0)
        set_cursor((pageno - (remainder ? 0 : 1)) * dhexview_options.page_size);
    return;
}

static void end_of_line(void)
{
    cursorOffset = 0;
    if (!move_cursor(lineLength - 1 - cursor % lineLength))
        move_cursor(nbBytes - cursor);
}

static void scroll_up(void)
{
    move_base(+page);

    if (mark_set)
        updateMarked();
}

static void scroll_down(void)
{
    move_base(-page);

    if (mark_set)
        updateMarked();
}

static void beginning_of_buffer(void)
{
    cursorOffset = 0;
    set_cursor(0);
}

static void end_of_buffer(void)
{
    INT s = getfilesize();
    cursorOffset = 0;
    set_cursor(s);
}

static void suspend(void)
{
    kill(getpid(), SIGTSTP);
}
static void undo(void)
{
    discardEdited();
    readFile();
}
static void toggle(void)
{
    hexOrAscii = (hexOrAscii + 1) % 2;
}

static void recenter(void)
{
    if (cursor) {
        base = base + cursor;
        cursor = 0;
        readFile();
    }
}

static void find_file(void)
{
    if (!ask_about_save_and_redisplay())
        return;
    if (!findFile()) {
        displayMessageAndWaitForKey("No such file or directory");
        return;
    }
    openFile();
    readFile();
}

static void redisplay(void)
{
    clear();
}

static void firstTimeHelp(void)
{
    int firstTime = true;

    if (firstTime) {
        firstTime = false;
        short_help();
    }
}

static void set_mark_command(void)
{
    unmarkAll();
    if ((mark_set = not(mark_set))) {
        markIt(cursor);
        mark_min = mark_max = base + cursor;
    }
}

static void save_bookmark(void)
{
    char buf[BOOKMARK_NAME_MAX + 1];
    char *last = NULL;

    if (!displayMessageAndGetString("Bookmark this location: ",
                                    &last, buf, sizeof(buf))) {
        return;
    }

    if (!bms->add_bookmark(bms, buf, base + cursor))
        displayMessageAndWaitForKey("This bookmark already exists");

    return;
}

/* Remember last bookmark we went to */
static char *_last_bookmark = NULL;

static void goto_bookmark(void)
{
    char buf[BOOKMARK_NAME_MAX + 1];

    if (!displayMessageAndGetString("Go to bookmark: ", &_last_bookmark,
                                    buf, sizeof(buf))) {
        return;
    }

    bookmark_t b;
    if (bms->get_bookmark_by_name(bms, buf, &b))
        set_cursor(b.cursor);
    else
        displayMessageAndWaitForKey("No such bookmark");

    return;
}

static void list_bookmarks(void)
{
    char buf[2 + /* "%1d " */ +18 /* 0x%016X */  + 2 /* ": " */  +
             BOOKMARK_NAME_MAX + 1];
    int i;
    INT c;

    int nb = bms->count(bms);
    if (0 == nb) {
        displayMessageAndWaitForKey("You have no bookmarks");
        return;
    }

    /* FIXME */
    /* Display bookmarks on per line.  We hope there are not more than the
     * number of lines.
     */
    for (i = 0; i < nb; ++i) {
        bookmark_t b;
        if (!bms->get_bookmark_by_number(bms, i, &b))
            break;

        if (i < 10) {
            snprintf(buf, sizeof(buf), "%1d 0x%016lX: %s",
                     i, b.cursor, b.name);
        } else {
            snprintf(buf, sizeof(buf), "  0x%016lX: %s",
                     b.cursor, b.name);
        }
        displayCentered(buf, i);
    }
    displayCentered("Use keys 0 - 9 to go to first 10 bookmarks", i);

    c = getch();
    /* First 10 bookmarks have shortcuts associated with them */
    if (c >= '0' && c <= '9') {
        bookmark_t b;
        int n = c - '0';
        if (n < nb && bms->get_bookmark_by_number(bms, n, &b)) {
            set_cursor(b.cursor);
        }
    }

    return;
}

static void clear_bookmarks(void)
{
    displayOneLineMessage("Really clear bookmarks (Yes/No/Cancel) ?");

    if ('y' != tolower(getch()))
        return;

    bms->delete_all(bms);

    return;
}

/*
 * Move backward or forward based on the value of integer under cursor.
 * This function supports reading 2-byte or 4-byte integers (the size of
 * the integer is specified by the first argument).
 */
static void move_by_cursor(int sz, int forward)
{
    off_t off = 0;

    LSEEK(fd, cursor + base);
    read(fd, &off, sz);

#if 0
    char str[200];
    sprintf(str, "Fill move %s by 0x%X bytes",
            (forward ? "forward" : "backward"), off);
    displayMessageAndWaitForKey(str);
#endif

    if (!move_cursor(forward ? off : -off))
        displayMessageAndWaitForKey("Invalid location!");

    return;
}


/****************************************************
 ask_about_* or functions that present a prompt
****************************************************/


int ask_about_save(void)
{
    if (edited) {
        displayOneLineMessage("Save changes (Yes/No/Cancel) ?");

        switch (tolower(getch())) {
        case 'y':
            save_buffer();
            break;
        case 'n':
            discardEdited();
            break;

        default:
            return false;
        }
        return true;
    }
    return -true;
}

int ask_about_save_and_redisplay(void)
{
    int b = ask_about_save();
    if (b == true) {
        readFile();
        display();
    }
    return b;
}

void ask_about_save_and_quit(void)
{
    if (ask_about_save())
        quit();
}

static void goto_char(char dir)
{
    INT i;

    displayOneLineMessage("New position ? ");
    if ('+' == dir)
        ungetstr("+0x");
    else if ('-' == dir)
        ungetstr("-0x");
    else
        ungetstr("0x");
    if (!get_number(&i) || !set_cursor(i))
        displayMessageAndWaitForKey("Invalid position!");
}

static void save_buffer(void)
{
    int displayedmessage = false;
    typePage *p, *q;
    for (p = edited; p; p = q) {
        if (LSEEK_(fd, p->base) == -1 || write(fd, p->vals, p->size) == -1)
            if (!displayedmessage) {    /* It would be annoying to display lots of error messages when we can't write. */
                displayMessageAndWaitForKey(strerror(errno));
                displayedmessage = true;
            }
        q = p->next;
        freePage(p);
    }
    edited = NULL;
    if (lastEditedLoc > fileSize)
        fileSize = lastEditedLoc;
    lastEditedLoc = 0;
    memset(bufferAttr, A_NORMAL, page * sizeof(*bufferAttr));
    if (displayedmessage) {
        displayMessageAndWaitForKey
            ("Unwritten changes have been discarded");
        readFile();
        if (cursor > nbBytes)
            set_cursor(getfilesize());
    }
    if (mark_set)
        markSelectedRegion();
}

static void help(void)
{
    char *args[3];
    int status;

    args[0] = "man";
    args[1] = "hexedit";
    args[2] = NULL;
    endwin();
    if (fork() == 0) {
        execvp(args[0], args);
        exit(1);
    }
    wait(&status);
    refresh();
    raw();
}

static void short_help(void)
{
    displayMessageAndWaitForKey("Unknown command, press F1 for help");
}



/*******************************************************************************/
/* key_to_function */
/*******************************************************************************/
int key_to_function(int key)
{
    oldcursor = cursor;
    oldcursorOffset = cursorOffset;
    oldbase = base;
    /*printf("*******%d******\n", key); */

    switch (key) {
    case KEY_RIGHT:
    case CTRL('F'):
        move_cursor(+1);
        break;

    case 't':
        if (dhexview_options.alternate_toolbar)
            dhexview_options.alternate_toolbar = false;
        else
            dhexview_options.alternate_toolbar = true;
        break;

    case KEY_LEFT:
    case CTRL('B'):
        move_cursor(-1);
        break;

    case KEY_DOWN:
    case CTRL('N'):
        next_line();
        break;

    case KEY_UP:
    case CTRL('P'):
        previous_line();
        break;

    case ALT('F'):
        forward_chars();
        break;

    case ALT('B'):
        backward_chars();
        break;

    case ALT('N'):
        next_lines();
        break;

    case ALT('P'):
        previous_lines();
        break;

    case CTRL('A'):
    case KEY_HOME:
        beginning_of_line();
        break;

    case CTRL('E'):
    case KEY_END:
        end_of_line();
        break;

    case KEY_NPAGE:
    case CTRL('V'):
    case KEY_F(6):
        scroll_up();
        break;

    case KEY_PPAGE:
    case ALT('V'):
    case KEY_F(5):
        scroll_down();
        break;

    case '<':
    case ALT('<'):
        beginning_of_buffer();
        break;

    case '>':
    case ALT('>'):
        end_of_buffer();
        break;

    case KEY_SUSPEND:
    case CTRL('Z'):
        suspend();
        break;

    case CTRL('U'):
    case CTRL('_'):
        undo();
        break;

    case CTRL('T'):
    case '\t':
        toggle();
        break;

    case 'H':
        if (hlsearch_crulist)
            hlsearch_crulist->destroy_rules(hlsearch_crulist);
        break;

    case '/':
        search_forward(false);
        break;

    case 'n':
        search_forward(true);
        break;

    case '?':
        search_backward(false);
        break;

    case 'N':
        search_backward(true);
        break;

    case CTRL('G'):
    case KEY_F(4):
        goto_char(0);
        break;

    case ALT('L'):
        recenter();
        break;

    case '\n':
    case '\r':
    case KEY_ENTER:
        goto_char(0);
        break;

    case '-':
        goto_char('-');
        break;

    case '+':
        goto_char('+');
        break;

    case CTRL('W'):
    case KEY_F(2):
        save_buffer();
        break;

    case CTRL('['):            /* escape */
        escaped_command();
        break;

    case KEY_F(1):
    case ALT('H'):
        help();
        break;

    case KEY_F(3):
    case CTRL('O'):
        find_file();
        break;

    case CTRL('L'):
        redisplay();
        break;

    case CTRL(' '):
    case KEY_F(9):
        set_mark_command();
        break;

    case CTRL('D'):
    case ALT('W'):
    case KEY_DC:
    case KEY_F(7):
    case 0x7F:                 /* found on a sun */
        copy_region();
        break;

    case CTRL('Y'):
    case KEY_IC:
    case KEY_F(8):
        yank();
        break;

    case ALT('Y'):
    case KEY_F(11):
        yank_to_a_file();
        break;

    case KEY_F(12):
    case ALT('I'):
        fill_with_string();
        break;

    case CTRL('C'):
        quit();
        break;

    case KEY_F(0):
    case KEY_F(10):
    case CTRL('X'):
        ask_about_save_and_quit();
        break;

    case ']':
        move_by_cursor(4, 1);
        break;

    case '[':
        move_by_cursor(4, 0);
        break;

    case '}':
        move_by_cursor(2, 1);
        break;

    case '{':
        move_by_cursor(2, 0);
        break;

    case 'p':
        move_to_next_page();
        recenter();
        break;

    case 'P':
        move_to_prev_page();
        recenter();
        break;

    case 'C':
        if (colored)
            colored = false;
        else
            colored = true;
        break;

    case 'm':
        save_bookmark();
        break;

    case 'g':
        goto_bookmark();
        break;

    case 'l':
        list_bookmarks();
        break;

    case 'X':
        clear_bookmarks();
        break;

    case 'b': {
        uint64_t position;
        if (position_history->prev(position_history, &position)) {
            set_cursor(position);
        }
        break;
    }

    case 'f': {
        uint64_t position;
        if (position_history->next(position_history, &position)) {
            set_cursor(position);
        }
        break;
    }

    default:
        short_help();
        break;
    }

    return true;
}



static void escaped_command(void)
{
    char tmp[BLOCK_SEARCH_SIZE];
    int c, i;

    c = getch();
    switch (c) {
    case 'f':
        forward_chars();
        break;

    case 'b':
        backward_chars();
        break;

    case KEY_DOWN:
    case 'n':
        next_lines();
        break;

    case KEY_UP:
    case 'p':
        previous_lines();
        break;

    case 'v':
        scroll_down();
        break;

    case KEY_HOME:
    case '<':
        beginning_of_buffer();
        break;

    case KEY_END:
    case '>':
        end_of_buffer();
        break;

    case 'l':
        recenter();
        break;

    case 'h':
        help();
        break;

    case 'w':
        copy_region();
        break;

    case 'y':
        yank_to_a_file();
        break;

    case 'i':
        fill_with_string();
        break;

    case '':
        c = getch();
        if (c == 'O') {
            switch (c = getch()) {
            case 'C':
                forward_chars();
                break;

            case 'D':
                backward_chars();
                break;

            case 'B':
                next_lines();
                break;

            case 'A':
                previous_lines();
                break;

            case 'H':
                beginning_of_buffer();
                break;

            case 'F':
                end_of_buffer();
                break;

            case 'P':          /* F1 on a xterm */
                help();
                break;

            case 'Q':          /* F2 on a xterm */
                save_buffer();
                break;

            case 'R':          /* F3 on a xterm */
                find_file();
                break;

            case 'S':          /* F4 on a xterm */
                goto_char(0);
                break;

            default:
                firstTimeHelp();
            }
        } else
            firstTimeHelp();
        break;

    case '[':
        for (i = 0;; i++) {
            tmp[i] = c = getch();
            if (!isdigit(c))
                break;
        }
        tmp[i + 1] = '\0';

        if (0);
        else if (streq(tmp, "2~"))
            yank();
        else if (streq(tmp, "5~"))
            scroll_down();
        else if (streq(tmp, "6~"))
            scroll_up();
        else if (streq(tmp, "7~"))
            beginning_of_buffer();
        else if (streq(tmp, "8~"))
            end_of_buffer();
        else if (streq(tmp, "010q" /* F10 on a sgi's winterm */ ))
            ask_about_save_and_quit();
        else if (streq(tmp, "193z"))
            fill_with_string();
        else if (streq(tmp, "214z"))
            beginning_of_line();
        else if (streq(tmp, "216z"))
            scroll_down();
        else if (streq(tmp, "220z"))
            end_of_line();
        else if (streq(tmp, "222z"))
            scroll_up();
        else if (streq(tmp, "233z"))
            ask_about_save_and_quit();
        else if (streq(tmp, "234z" /* F11 on a sun */ ))
            yank_to_a_file();
        else if (streq(tmp, "247z"))
            yank();
        else if (streq(tmp, "11~" /* F1 on a rxvt */ ))
            help();
        else if (streq(tmp, "12~" /* F2 on a rxvt */ ))
            save_buffer();
        else if (streq(tmp, "13~" /* F3 on a rxvt */ ))
            find_file();
        else if (streq(tmp, "14~" /* F4 on a rxvt */ ))
            goto_char(0);
        else if (streq(tmp, "15~" /* F5 on a rxvt */ ))
            scroll_down();
        else if (streq(tmp, "17~" /* F6 on a rxvt */ ))
            scroll_up();
        else if (streq(tmp, "18~" /* F7 on a rxvt */ ))
            copy_region();
        else if (streq(tmp, "19~" /* F8 on a rxvt */ ))
            yank();
        else if (streq(tmp, "20~" /* F9 on a rxvt */ ))
            set_mark_command();
        else if (streq(tmp, "21~" /* F10 on a rxvt */ ))
            ask_about_save_and_quit();
        else if (streq(tmp, "23~" /* F11 on a rxvt */ ))
            yank_to_a_file();
        else if (streq(tmp, "24~" /* F12 on a rxvt */ ))
            fill_with_string();
        else
            firstTimeHelp();
        break;

    default:
        firstTimeHelp();
    }
}
